Skip to content

Conversation

@schiller-manuel
Copy link
Contributor

@schiller-manuel schiller-manuel commented Jan 10, 2026

fixes #3023

Summary by CodeRabbit

  • New Features

    • Dev-mode route-scoped CSS collection and serving for improved SSR styling across React, Solid, and Vue.
    • New example projects demonstrating CSS Modules integration for React, Solid, and Vue with Vite configs.
  • Tests

    • End-to-end test suites added to validate global CSS and CSS Modules behavior, hydration, and client-side navigation in all three framework samples.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 10, 2026

📝 Walkthrough

Walkthrough

Adds CSS Modules e2e projects for React, Solid, and Vue; implements a dev-mode CSS collector and middleware to serve route-scoped CSS from the Vite dev server; and updates framework HeadContent components to inject a dev-only stylesheet link that is removed after hydration. Includes Playwright e2e tests and TypeScript/Vite configs for each example.

Changes

Cohort / File(s) Summary
React Start E2E Project
e2e/react-start/css-modules/{.gitignore,.prettierignore,package.json,vite.config.ts,tsconfig.json,playwright.config.ts,src/router.tsx,src/routes/__root.tsx,src/routes/index.tsx,src/routes/modules.tsx,src/styles/global.css,src/styles/card.module.css,tests/css.spec.ts,tests/setup/global.setup.ts,tests/setup/global.teardown.ts}
New React e2e app demonstrating CSS Modules: project configs, routes, global and module styles, Playwright config, and comprehensive tests validating SSR, hydration, and client navigation styling.
Solid Start E2E Project
e2e/solid-start/css-modules/{.gitignore,.prettierignore,package.json,vite.config.ts,tsconfig.json,playwright.config.ts,src/router.tsx,src/routes/__root.tsx,src/routes/index.tsx,src/routes/modules.tsx,src/styles/global.css,src/styles/card.module.css,tests/css.spec.ts,tests/setup/global.setup.ts,tests/setup/global.teardown.ts}
New Solid e2e app mirroring React project with Solid-specific routes, Vite/TS configs, Playwright setup, and tests for SSR/module/global CSS behavior.
Vue Start E2E Project
e2e/vue-start/css-modules/{.gitignore,.prettierignore,package.json,vite.config.ts,tsconfig.json,playwright.config.ts,src/router.tsx,src/routes/__root.tsx,src/routes/index.tsx,src/routes/modules.tsx,src/styles/global.css,src/styles/card.module.css,tests/css.spec.ts,tests/setup/global.setup.ts,tests/setup/global.teardown.ts}
New Vue e2e app with CSS Modules example, routes, Vite/TS configs, Playwright config, and tests verifying SSR and hydration of global and module styles.
Dev Styles Collection
packages/start-plugin-core/src/dev-server-plugin/dev-styles.ts
New module to crawl the Vite dev server module graph, perform SSR transforms as needed, extract and unescape per-file CSS, and aggregate CSS content keyed by module URL for route-scoped CSS generation. Exports CollectDevStylesOptions and collectDevStyles.
Dev Server Plugin
packages/start-plugin-core/src/dev-server-plugin/plugin.ts
devServerPlugin updated to use GetConfigFn and adds middleware endpoint /@tanstack-start/styles.css that validates requested route IDs against a manifest, invokes collectDevStyles for entries, and serves concatenated CSS with no-store cache headers.
HeadContent Components
packages/react-router/src/HeadContent.tsx, packages/solid-router/src/HeadContent.tsx, packages/vue-router/src/HeadContent.tsx
Added DevStylesLink component and wired it into HeadContent in non-production builds. Renders a route-scoped dev stylesheet link (data-tanstack-start-dev-styles) with SSR/client-consistent href and removes the SSR link after hydration to avoid duplication.
Tests / Expectations
packages/react-router/tests/Scripts.test.tsx
Updated test assertions to account for the new dev styles link in SSR HeadContent output.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Browser/Client
  participant Middleware as Dev Middleware (/@@styles.css)
  participant Vite as Vite Dev Server
  participant Graph as Module Graph
  participant Router as Router State / Route Manifest

  Browser->>Middleware: GET /@tanstack-start/styles.css?routes=...
  Middleware->>Router: Validate & resolve route IDs (manifest)
  Middleware->>Vite: collectDevStyles(entries)
  Vite->>Graph: Traverse entries -> ensure SSR transforms
  Graph->>Graph: Extract CSS from transformed modules
  Graph-->>Vite: Return per-file CSS content
  Vite-->>Middleware: Aggregated CSS payload
  Middleware-->>Browser: Respond text/css (no-store)
  Browser->>Browser: HeadContent renders DevStylesLink (href to endpoint)
  Browser->>Browser: After hydration remove SSR link to avoid duplication
  Browser->>Middleware: On navigation, request styles for new routes
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • birkskyum
  • lachlancollins
  • brenelz

Poem

🐰
I hopped through code at break of dawn,
Collected styles so FOUC is gone.
Per-route CSS, neat and spry,
Hydration smiles, no frantic cry.
A rabbit cheers — styles served on time!

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: collect styles in dev' directly relates to the main change in the changeset, which implements CSS style collection in development mode to fix CSS Modules SSR issues.
Linked Issues check ✅ Passed The PR implements CSS style collection in dev mode across multiple framework packages (React, Solid, Vue) and core plugins to address CSS Modules FOUC issues in SSR.
Out of Scope Changes check ✅ Passed All changes are scoped to CSS style collection in development: adding e2e test projects, implementing dev style collection in core plugins, and updating head content components across frameworks.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link

nx-cloud bot commented Jan 10, 2026

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit ae8fd59

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ❌ Failed 1m 51s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 2s View ↗

☁️ Nx Cloud last updated this comment at 2026-01-10 03:06:24 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 10, 2026

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@6343

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/eslint-plugin-router@6343

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@6343

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/nitro-v2-vite-plugin@6343

@tanstack/react-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router@6343

@tanstack/react-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@6343

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-ssr-query@6343

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@6343

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@6343

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@6343

@tanstack/router-cli

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-cli@6343

@tanstack/router-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-core@6343

@tanstack/router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools@6343

@tanstack/router-devtools-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools-core@6343

@tanstack/router-generator

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-generator@6343

@tanstack/router-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-plugin@6343

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-ssr-query-core@6343

@tanstack/router-utils

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-utils@6343

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-vite-plugin@6343

@tanstack/solid-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router@6343

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-devtools@6343

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-ssr-query@6343

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@6343

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@6343

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@6343

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@6343

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-fn-stubs@6343

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@6343

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@6343

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-static-server-functions@6343

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@6343

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@6343

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@6343

@tanstack/vue-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router@6343

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router-devtools@6343

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router-ssr-query@6343

@tanstack/vue-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start@6343

@tanstack/vue-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-client@6343

@tanstack/vue-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-server@6343

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@6343

commit: ae8fd59

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @e2e/vue-start/css-modules/package.json:
- Around line 16-20: Update the internal dependency versions in package.json
that use the workspace protocol from "workspace:^" to "workspace:*" —
specifically replace "@tanstack/vue-router", "@tanstack/vue-start", and
"@tanstack/router-e2e-utils" (and any other internal @tanstack/* entries on
lines 21-30) to use "workspace:*" so they follow the repo guideline for internal
workspace dependencies.

In @packages/start-plugin-core/src/dev-server-plugin/dev-styles.ts:
- Around line 146-158: The loadCssContent function currently calls
vite.transformRequest(node.url) which can throw and break the entire styles
traversal; wrap that call in a try/catch inside loadCssContent so any thrown
error is caught, optionally log the error (or debug) and return undefined to
make CSS loading best-effort, and keep the existing check for
transformResult?.code before calling extractCssFromViteModule; ensure no
transformRequest exceptions bubble out of loadCssContent.

In @packages/start-plugin-core/src/dev-server-plugin/plugin.ts:
- Around line 79-124: The middleware should only match the exact styles route
and handle async errors: replace the loose startsWith check with an exact
pathname check (e.g., ensure new URL(req.url, base).pathname ===
'/@tanstack-start/styles.css' and optionally verify req.method === 'GET'), and
wrap the URL parsing + collectDevStyles call in a try/catch so exceptions don't
become unhandled; on error log via viteDevServer.config.logger.error (or
console.error) and respond with a 500 status and an empty body. Keep the
existing route manifest lookup (TSS_ROUTES_MANIFEST) and
entries/collectDevStyles usage, but move the new URL(...) and await
collectDevStyles(...) inside the try block and only call res.setHeader/res.end
in the normal path (use res.statusCode = 500; res.end('') in the catch).
🧹 Nitpick comments (12)
e2e/vue-start/css-modules/src/router.tsx (1)

4-12: Good SSR-friendly router factory; refactor suggestion to inline return available.
Factory pattern is appropriate for SSR isolation. Both scrollRestoration and defaultPreload are valid createRouter options.

Proposed refactor (no behavior change)
 export function getRouter() {
-  const router = createRouter({
+  return createRouter({
     routeTree,
     scrollRestoration: true,
     defaultPreload: false,
   })
-
-  return router
 }
e2e/solid-start/css-modules/tsconfig.json (1)

1-24: Solid e2e tsconfig looks solid; consider dropping allowJs unless you truly have JS sources.

strict: true, moduleResolution: "Bundler", and jsxImportSource: "solid-js" are aligned for Vite/Solid. If there are no .js inputs, allowJs: true is just extra surface area for less-typed code.

e2e/react-start/css-modules/tests/css.spec.ts (1)

73-84: Consider using more deterministic waiting strategies instead of arbitrary timeouts.

Lines 77, 90, and 104 use page.waitForTimeout(1000) to wait for hydration. While this works, it can lead to flaky tests in slower CI environments or unnecessarily slow tests in fast environments.

♻️ More reliable alternatives

Consider one of these approaches:

Option 1: Wait for network idle

-    await page.waitForTimeout(1000)
+    await page.waitForLoadState('networkidle')

Option 2: Wait for a hydration marker
Add a data attribute when hydration completes and wait for it:

-    await page.waitForTimeout(1000)
+    await page.waitForSelector('[data-hydrated="true"]')

Option 3: Wait for interactive state

-    await page.waitForTimeout(1000)
+    await page.waitForLoadState('domcontentloaded')

Apply similar changes to lines 90 and 104.

e2e/solid-start/css-modules/tests/css.spec.ts (1)

77-77: Consider using deterministic waits instead of fixed timeouts.

The test uses waitForTimeout(1000) to wait for hydration. Fixed timeouts can make tests flaky and slower than necessary. Consider waiting for specific hydration indicators instead:

♻️ More reliable hydration detection
-    // Wait for hydration
-    await page.waitForTimeout(1000)
+    // Wait for hydration by checking for React's hydration marker or a specific event
+    await page.waitForLoadState('networkidle')

Alternatively, if there's a specific hydration indicator in your app (e.g., a data attribute or class added post-hydration), wait for that:

await page.waitForSelector('[data-hydrated="true"]', { state: 'attached' })

This makes tests more deterministic and potentially faster.

Also applies to: 90-90, 104-104

e2e/vue-start/css-modules/tests/css.spec.ts (1)

77-77: Consider using deterministic waits instead of fixed timeouts.

Similar to the Solid test suite, this test uses waitForTimeout(1000) for hydration waits. Deterministic waits (e.g., waitForLoadState('networkidle') or waiting for specific hydration markers) would make the tests more reliable and potentially faster.

Also applies to: 90-90, 104-104

packages/react-router/src/HeadContent.tsx (1)

206-237: Avoid removing a React-owned DOM node; make the component stop rendering after mount instead.

useEffect(...querySelectorAll(...).remove()) deletes the same <link> React just hydrated/created, but DevStylesLink() keeps returning that <link> on every render. This can cause DOM/VDOM divergence and unpredictable updates during client navigations.

Proposed fix (React-controlled removal)
 function DevStylesLink() {
   const router = useRouter()
+  const [hydrated, setHydrated] = React.useState(false)

   const routeIds = useRouterState({
     select: (state) => state.matches.map((match) => match.routeId),
   })

   React.useEffect(() => {
-    // After hydration, remove the SSR-rendered dev styles link
-    document
-      .querySelectorAll('[data-tanstack-start-dev-styles]')
-      .forEach((el) => el.remove())
+    setHydrated(true)
   }, [])

+  if (hydrated) return null
+
   // Build the same href on both server and client for hydration match
   const href = `/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(','))}`

   return (
     <link
       rel="stylesheet"
       href={href}
       data-tanstack-start-dev-styles
       suppressHydrationWarning
     />
   )
 }
packages/vue-router/src/HeadContent.tsx (1)

9-42: Avoid manual DOM removal; make DevStylesLink render nothing after mount.

onMounted(...remove()) deletes the hydrated node while the component still renders it, risking Vue patching inconsistencies.

Proposed fix (Vue-controlled removal)
 const DevStylesLink = Vue.defineComponent({
   name: 'DevStylesLink',
   setup() {
+    const hydrated = Vue.ref(false)
     const routeIds = useRouterState({
       select: (state) => state.matches.map((match) => match.routeId),
     })

     Vue.onMounted(() => {
-      // After hydration, remove the SSR-rendered dev styles link
-      document
-        .querySelectorAll('[data-tanstack-start-dev-styles]')
-        .forEach((el) => el.remove())
+      hydrated.value = true
     })

     // Build the same href on both server and client for hydration match
     const href = Vue.computed(
       () =>
         `/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.value.join(','))}`,
     )

     return () =>
+      hydrated.value
+        ? null
+        :
       Vue.h('link', {
         rel: 'stylesheet',
         href: href.value,
         'data-tanstack-start-dev-styles': true,
       })
   },
 })
packages/solid-router/src/HeadContent.tsx (1)

200-223: Avoid manual DOM removal; make DevStylesLink stop rendering after mount.

As written, Solid hydrates/renders <link ...data-tanstack-start-dev-styles />, then onMount removes it from the DOM while the component still returns it.

Proposed fix (Solid-controlled removal)
 function DevStylesLink() {
   const routeIds = useRouterState({
     select: (state) => state.matches.map((match) => match.routeId),
   })
+  const [hydrated, setHydrated] = Solid.createSignal(false)

   onMount(() => {
-    // After hydration, remove the SSR-rendered dev styles link
-    document
-      .querySelectorAll('[data-tanstack-start-dev-styles]')
-      .forEach((el) => el.remove())
+    setHydrated(true)
   })

+  if (hydrated()) return null
+
   // Build the same href on both server and client for hydration match
   const href = () =>
     `/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds().join(','))}`

   return <link rel="stylesheet" href={href()} data-tanstack-start-dev-styles />
 }

Also applies to: 234-238

packages/start-plugin-core/src/dev-server-plugin/dev-styles.ts (3)

76-100: Key visited by URL (string), not ModuleNode identity (and avoid reassigning node).

You visited.add(node) and later potentially replace node via getModuleByUrl. If Vite returns a different ModuleNode instance, the visited guard can be bypassed and traversal work can balloon.

Proposed fix (URL-based visited + no param reassignment)
 async function crawlModuleForCss(
   vite: ViteDevServer,
   node: ModuleNode,
-  visited: Set<ModuleNode>,
+  visited: Set<string>,
   styles: Map<string, string>,
 ): Promise<void> {
-  if (visited.has(node)) return
-  visited.add(node)
+  const nodeKey = node.url
+  if (visited.has(nodeKey)) return
+  visited.add(nodeKey)

   const branches: Array<Promise<void>> = []

   // Ensure the module has been transformed to populate its deps
   // This is important for code-split modules that may not have been processed yet
-  if (!node.ssrTransformResult) {
+  let currentNode = node
+  if (!currentNode.ssrTransformResult) {
     try {
-      await vite.transformRequest(node.url, { ssr: true })
+      await vite.transformRequest(currentNode.url, { ssr: true })
       // Re-fetch the node to get updated state
-      const updatedNode = await vite.moduleGraph.getModuleByUrl(node.url)
+      const updatedNode = await vite.moduleGraph.getModuleByUrl(currentNode.url)
       if (updatedNode) {
-        node = updatedNode
+        currentNode = updatedNode
       }
     } catch {
       // Ignore transform errors - the module might not be transformable
     }
   }

   // Check if this is a CSS file
   if (
-    node.file &&
-    isCssFile(node.file) &&
-    !hasCssSideEffectFreeParam(node.url)
+    currentNode.file &&
+    isCssFile(currentNode.file) &&
+    !hasCssSideEffectFreeParam(currentNode.url)
   ) {
-    const css = await loadCssContent(vite, node)
+    const css = await loadCssContent(vite, currentNode)
     if (css) {
-      styles.set(node.url, css)
+      styles.set(currentNode.url, css)
     }
   }

   const depsFromSsr = node.ssrTransformResult?.deps ?? []

(Also update collectDevStyles to const visited = new Set<string>().)

Also applies to: 82-96


17-28: Treat “side-effect-free” query params as present regardless of value.

Right now hasCssSideEffectFreeParam only filters ?raw / ?url when the param is valueless (and not ?raw=). If user code uses ?raw=true, this will still be collected/injected.

Proposed fix
 function hasCssSideEffectFreeParam(url: string): boolean {
   const queryString = url.split('?')[1]
   if (!queryString) return false

   const params = new URLSearchParams(queryString)
-  return CSS_SIDE_EFFECT_FREE_PARAMS.some(
-    (param) =>
-      params.get(param) === '' &&
-      !url.includes(`?${param}=`) &&
-      !url.includes(`&${param}=`),
-  )
+  return CSS_SIDE_EFFECT_FREE_PARAMS.some((param) => params.has(param))
 }

160-175: CSS extraction via regex + manual unescape is brittle; prefer parsing the literal.

This assumes Vite emits const __vite__css = "..."; and only unescapes a small subset of escape sequences. If Vite changes emitted syntax (e.g., let, different naming, or uses \r, \uXXXX, etc.), dev CSS collection silently fails.

  • Verify (for your Vite version) that transformed CSS modules always contain a __vite__css string declared via const.
  • If yes, consider making extraction robust by parsing the JS and reading the literal value (AST), rather than regex + partial unescaping.
packages/start-plugin-core/src/dev-server-plugin/plugin.ts (1)

89-96: Consider bounding the routes query param size (dev-only but easy to footgun).

A very large routes list can force expensive graph crawling per request.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d904d6 and e231ba1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (50)
  • e2e/react-start/css-modules/.gitignore
  • e2e/react-start/css-modules/.prettierignore
  • e2e/react-start/css-modules/package.json
  • e2e/react-start/css-modules/playwright.config.ts
  • e2e/react-start/css-modules/src/router.tsx
  • e2e/react-start/css-modules/src/routes/__root.tsx
  • e2e/react-start/css-modules/src/routes/index.tsx
  • e2e/react-start/css-modules/src/routes/modules.tsx
  • e2e/react-start/css-modules/src/styles/card.module.css
  • e2e/react-start/css-modules/src/styles/global.css
  • e2e/react-start/css-modules/tests/css.spec.ts
  • e2e/react-start/css-modules/tests/setup/global.setup.ts
  • e2e/react-start/css-modules/tests/setup/global.teardown.ts
  • e2e/react-start/css-modules/tsconfig.json
  • e2e/react-start/css-modules/vite.config.ts
  • e2e/solid-start/css-modules/.gitignore
  • e2e/solid-start/css-modules/.prettierignore
  • e2e/solid-start/css-modules/package.json
  • e2e/solid-start/css-modules/playwright.config.ts
  • e2e/solid-start/css-modules/src/router.tsx
  • e2e/solid-start/css-modules/src/routes/__root.tsx
  • e2e/solid-start/css-modules/src/routes/index.tsx
  • e2e/solid-start/css-modules/src/routes/modules.tsx
  • e2e/solid-start/css-modules/src/styles/card.module.css
  • e2e/solid-start/css-modules/src/styles/global.css
  • e2e/solid-start/css-modules/tests/css.spec.ts
  • e2e/solid-start/css-modules/tests/setup/global.setup.ts
  • e2e/solid-start/css-modules/tests/setup/global.teardown.ts
  • e2e/solid-start/css-modules/tsconfig.json
  • e2e/solid-start/css-modules/vite.config.ts
  • e2e/vue-start/css-modules/.gitignore
  • e2e/vue-start/css-modules/.prettierignore
  • e2e/vue-start/css-modules/package.json
  • e2e/vue-start/css-modules/playwright.config.ts
  • e2e/vue-start/css-modules/src/router.tsx
  • e2e/vue-start/css-modules/src/routes/__root.tsx
  • e2e/vue-start/css-modules/src/routes/index.tsx
  • e2e/vue-start/css-modules/src/routes/modules.tsx
  • e2e/vue-start/css-modules/src/styles/card.module.css
  • e2e/vue-start/css-modules/src/styles/global.css
  • e2e/vue-start/css-modules/tests/css.spec.ts
  • e2e/vue-start/css-modules/tests/setup/global.setup.ts
  • e2e/vue-start/css-modules/tests/setup/global.teardown.ts
  • e2e/vue-start/css-modules/tsconfig.json
  • e2e/vue-start/css-modules/vite.config.ts
  • packages/react-router/src/HeadContent.tsx
  • packages/solid-router/src/HeadContent.tsx
  • packages/start-plugin-core/src/dev-server-plugin/dev-styles.ts
  • packages/start-plugin-core/src/dev-server-plugin/plugin.ts
  • packages/vue-router/src/HeadContent.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript strict mode with extensive type safety for all code

Files:

  • e2e/react-start/css-modules/tests/setup/global.teardown.ts
  • e2e/solid-start/css-modules/playwright.config.ts
  • e2e/solid-start/css-modules/vite.config.ts
  • e2e/vue-start/css-modules/src/router.tsx
  • e2e/solid-start/css-modules/src/router.tsx
  • e2e/react-start/css-modules/tests/setup/global.setup.ts
  • e2e/vue-start/css-modules/tests/css.spec.ts
  • e2e/react-start/css-modules/src/router.tsx
  • e2e/solid-start/css-modules/tests/css.spec.ts
  • e2e/solid-start/css-modules/tests/setup/global.teardown.ts
  • e2e/vue-start/css-modules/tests/setup/global.setup.ts
  • packages/vue-router/src/HeadContent.tsx
  • e2e/react-start/css-modules/tests/css.spec.ts
  • packages/start-plugin-core/src/dev-server-plugin/plugin.ts
  • e2e/solid-start/css-modules/src/routes/modules.tsx
  • e2e/react-start/css-modules/vite.config.ts
  • packages/start-plugin-core/src/dev-server-plugin/dev-styles.ts
  • e2e/solid-start/css-modules/src/routes/__root.tsx
  • e2e/vue-start/css-modules/src/routes/modules.tsx
  • e2e/vue-start/css-modules/tests/setup/global.teardown.ts
  • e2e/vue-start/css-modules/src/routes/index.tsx
  • e2e/react-start/css-modules/src/routes/modules.tsx
  • e2e/solid-start/css-modules/src/routes/index.tsx
  • packages/solid-router/src/HeadContent.tsx
  • e2e/solid-start/css-modules/tests/setup/global.setup.ts
  • e2e/vue-start/css-modules/playwright.config.ts
  • e2e/vue-start/css-modules/src/routes/__root.tsx
  • e2e/react-start/css-modules/src/routes/__root.tsx
  • e2e/react-start/css-modules/playwright.config.ts
  • packages/react-router/src/HeadContent.tsx
  • e2e/react-start/css-modules/src/routes/index.tsx
  • e2e/vue-start/css-modules/vite.config.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Implement ESLint rules for router best practices using the ESLint plugin router

Files:

  • e2e/react-start/css-modules/tests/setup/global.teardown.ts
  • e2e/solid-start/css-modules/playwright.config.ts
  • e2e/solid-start/css-modules/vite.config.ts
  • e2e/vue-start/css-modules/src/router.tsx
  • e2e/solid-start/css-modules/src/router.tsx
  • e2e/react-start/css-modules/tests/setup/global.setup.ts
  • e2e/vue-start/css-modules/tests/css.spec.ts
  • e2e/react-start/css-modules/src/router.tsx
  • e2e/solid-start/css-modules/tests/css.spec.ts
  • e2e/solid-start/css-modules/tests/setup/global.teardown.ts
  • e2e/vue-start/css-modules/tests/setup/global.setup.ts
  • packages/vue-router/src/HeadContent.tsx
  • e2e/react-start/css-modules/tests/css.spec.ts
  • packages/start-plugin-core/src/dev-server-plugin/plugin.ts
  • e2e/solid-start/css-modules/src/routes/modules.tsx
  • e2e/react-start/css-modules/vite.config.ts
  • packages/start-plugin-core/src/dev-server-plugin/dev-styles.ts
  • e2e/solid-start/css-modules/src/routes/__root.tsx
  • e2e/vue-start/css-modules/src/routes/modules.tsx
  • e2e/vue-start/css-modules/tests/setup/global.teardown.ts
  • e2e/vue-start/css-modules/src/routes/index.tsx
  • e2e/react-start/css-modules/src/routes/modules.tsx
  • e2e/solid-start/css-modules/src/routes/index.tsx
  • packages/solid-router/src/HeadContent.tsx
  • e2e/solid-start/css-modules/tests/setup/global.setup.ts
  • e2e/vue-start/css-modules/playwright.config.ts
  • e2e/vue-start/css-modules/src/routes/__root.tsx
  • e2e/react-start/css-modules/src/routes/__root.tsx
  • e2e/react-start/css-modules/playwright.config.ts
  • packages/react-router/src/HeadContent.tsx
  • e2e/react-start/css-modules/src/routes/index.tsx
  • e2e/vue-start/css-modules/vite.config.ts
**/package.json

📄 CodeRabbit inference engine (AGENTS.md)

Use workspace protocol workspace:* for internal dependencies in package.json files

Files:

  • e2e/react-start/css-modules/package.json
  • e2e/vue-start/css-modules/package.json
  • e2e/solid-start/css-modules/package.json
🧠 Learnings (13)
📓 Common learnings
Learnt from: nlynzaad
Repo: TanStack/router PR: 6215
File: e2e/react-start/custom-basepath/package.json:13-17
Timestamp: 2025-12-25T13:04:55.492Z
Learning: In the TanStack Router repository, e2e test scripts are specifically designed to run in CI (which uses a Unix environment), so Unix-specific commands (like `rm -rf`, `&` for backgrounding, and direct environment variable assignments without `cross-env`) are acceptable in e2e test npm scripts.
📚 Learning: 2025-10-01T18:31:35.420Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: e2e/react-start/custom-basepath/src/routeTree.gen.ts:58-61
Timestamp: 2025-10-01T18:31:35.420Z
Learning: Do not review files named `routeTree.gen.ts` in TanStack Router repositories, as these are autogenerated files that should not be manually modified.

Applied to files:

  • e2e/react-start/css-modules/.prettierignore
  • e2e/react-start/css-modules/.gitignore
  • e2e/vue-start/css-modules/src/router.tsx
  • e2e/vue-start/css-modules/.gitignore
  • e2e/react-start/css-modules/src/router.tsx
  • e2e/vue-start/css-modules/.prettierignore
  • e2e/solid-start/css-modules/.prettierignore
  • e2e/solid-start/css-modules/.gitignore
  • e2e/vue-start/css-modules/src/routes/index.tsx
  • e2e/react-start/css-modules/src/routes/modules.tsx
  • e2e/react-start/css-modules/src/routes/__root.tsx
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • e2e/react-start/css-modules/.prettierignore
  • e2e/react-start/css-modules/.gitignore
  • e2e/vue-start/css-modules/src/router.tsx
  • e2e/solid-start/css-modules/src/router.tsx
  • e2e/vue-start/css-modules/.gitignore
  • e2e/react-start/css-modules/src/router.tsx
  • e2e/react-start/css-modules/tests/css.spec.ts
  • e2e/solid-start/css-modules/src/routes/modules.tsx
  • e2e/vue-start/css-modules/.prettierignore
  • e2e/solid-start/css-modules/.prettierignore
  • e2e/vue-start/css-modules/src/routes/modules.tsx
  • e2e/solid-start/css-modules/.gitignore
  • e2e/vue-start/css-modules/src/routes/index.tsx
  • e2e/react-start/css-modules/src/routes/modules.tsx
  • e2e/solid-start/css-modules/src/routes/index.tsx
  • e2e/vue-start/css-modules/src/routes/__root.tsx
  • e2e/react-start/css-modules/src/routes/__root.tsx
  • e2e/react-start/css-modules/src/routes/index.tsx
📚 Learning: 2025-11-02T16:16:24.898Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5732
File: packages/start-client-core/src/client/hydrateStart.ts:6-9
Timestamp: 2025-11-02T16:16:24.898Z
Learning: In packages/start-client-core/src/client/hydrateStart.ts, the `import/no-duplicates` ESLint disable is necessary for imports from `#tanstack-router-entry` and `#tanstack-start-entry` because both aliases resolve to the same placeholder file (`fake-start-entry.js`) in package.json during static analysis, even though they resolve to different files at runtime.

Applied to files:

  • e2e/react-start/css-modules/.prettierignore
  • e2e/react-start/css-modules/tests/setup/global.setup.ts
  • e2e/react-start/css-modules/package.json
  • e2e/react-start/css-modules/tsconfig.json
  • e2e/vue-start/css-modules/tests/setup/global.setup.ts
  • packages/start-plugin-core/src/dev-server-plugin/plugin.ts
  • e2e/vue-start/css-modules/.prettierignore
  • e2e/solid-start/css-modules/src/routes/__root.tsx
  • e2e/solid-start/css-modules/tsconfig.json
  • e2e/solid-start/css-modules/.prettierignore
  • e2e/vue-start/css-modules/package.json
  • e2e/vue-start/css-modules/tsconfig.json
  • e2e/vue-start/css-modules/src/routes/index.tsx
  • e2e/solid-start/css-modules/package.json
  • e2e/solid-start/css-modules/src/routes/index.tsx
  • e2e/solid-start/css-modules/tests/setup/global.setup.ts
  • e2e/react-start/css-modules/src/routes/__root.tsx
  • e2e/react-start/css-modules/src/routes/index.tsx
  • e2e/vue-start/css-modules/vite.config.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{js,ts,tsx} : Implement ESLint rules for router best practices using the ESLint plugin router

Applied to files:

  • e2e/react-start/css-modules/.prettierignore
  • e2e/vue-start/css-modules/src/router.tsx
  • e2e/solid-start/css-modules/src/router.tsx
  • e2e/react-start/css-modules/tsconfig.json
  • e2e/react-start/css-modules/src/router.tsx
  • e2e/solid-start/css-modules/src/routes/modules.tsx
  • e2e/vue-start/css-modules/.prettierignore
  • e2e/solid-start/css-modules/.prettierignore
  • e2e/vue-start/css-modules/src/routes/modules.tsx
  • e2e/vue-start/css-modules/src/routes/index.tsx
  • e2e/react-start/css-modules/src/routes/modules.tsx
  • e2e/solid-start/css-modules/src/routes/index.tsx
  • e2e/vue-start/css-modules/src/routes/__root.tsx
  • e2e/react-start/css-modules/src/routes/__root.tsx
  • e2e/react-start/css-modules/src/routes/index.tsx
📚 Learning: 2025-12-17T02:17:55.086Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 6120
File: packages/router-generator/src/generator.ts:654-657
Timestamp: 2025-12-17T02:17:55.086Z
Learning: In `packages/router-generator/src/generator.ts`, pathless_layout routes must receive a `path` property when they have a `cleanedPath`, even though they are non-path routes. This is necessary because child routes inherit the path from their parent, and without this property, child routes would not have the correct full path at runtime.

Applied to files:

  • e2e/react-start/css-modules/.prettierignore
  • e2e/solid-start/css-modules/src/router.tsx
  • e2e/react-start/css-modules/src/router.tsx
  • e2e/solid-start/css-modules/src/routes/modules.tsx
  • e2e/vue-start/css-modules/.prettierignore
  • e2e/solid-start/css-modules/src/routes/__root.tsx
  • e2e/solid-start/css-modules/.prettierignore
  • e2e/vue-start/css-modules/src/routes/index.tsx
  • e2e/solid-start/css-modules/src/routes/index.tsx
  • e2e/vue-start/css-modules/src/routes/__root.tsx
  • e2e/react-start/css-modules/src/routes/__root.tsx
  • e2e/react-start/css-modules/src/routes/index.tsx
📚 Learning: 2025-12-21T12:52:35.231Z
Learnt from: Sheraff
Repo: TanStack/router PR: 6171
File: packages/router-core/src/new-process-route-tree.ts:898-898
Timestamp: 2025-12-21T12:52:35.231Z
Learning: In `packages/router-core/src/new-process-route-tree.ts`, the matching logic intentionally allows paths without trailing slashes to match index routes with trailing slashes (e.g., `/a` can match `/a/` route), but not vice-versa (e.g., `/a/` cannot match `/a` layout route). This is implemented via the condition `!pathIsIndex || node.kind === SEGMENT_TYPE_INDEX` and is a deliberate design decision to provide better UX by being permissive with missing trailing slashes.

Applied to files:

  • e2e/react-start/css-modules/.prettierignore
  • e2e/vue-start/css-modules/.prettierignore
  • e2e/solid-start/css-modules/.prettierignore
  • e2e/vue-start/css-modules/src/routes/index.tsx
  • e2e/solid-start/css-modules/src/routes/index.tsx
  • e2e/vue-start/css-modules/src/routes/__root.tsx
  • e2e/react-start/css-modules/src/routes/__root.tsx
📚 Learning: 2025-12-25T13:04:55.492Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 6215
File: e2e/react-start/custom-basepath/package.json:13-17
Timestamp: 2025-12-25T13:04:55.492Z
Learning: In the TanStack Router repository, e2e test scripts are specifically designed to run in CI (which uses a Unix environment), so Unix-specific commands (like `rm -rf`, `&` for backgrounding, and direct environment variable assignments without `cross-env`) are acceptable in e2e test npm scripts.

Applied to files:

  • e2e/react-start/css-modules/tests/setup/global.teardown.ts
  • e2e/solid-start/css-modules/playwright.config.ts
  • e2e/react-start/css-modules/tests/setup/global.setup.ts
  • e2e/react-start/css-modules/package.json
  • e2e/vue-start/css-modules/tests/css.spec.ts
  • e2e/solid-start/css-modules/tests/css.spec.ts
  • e2e/solid-start/css-modules/tests/setup/global.teardown.ts
  • e2e/vue-start/css-modules/tests/setup/global.setup.ts
  • e2e/react-start/css-modules/tests/css.spec.ts
  • e2e/vue-start/css-modules/tests/setup/global.teardown.ts
  • e2e/vue-start/css-modules/package.json
  • e2e/solid-start/css-modules/package.json
  • e2e/solid-start/css-modules/tests/setup/global.setup.ts
  • e2e/vue-start/css-modules/playwright.config.ts
  • e2e/react-start/css-modules/playwright.config.ts
📚 Learning: 2025-10-09T12:59:14.842Z
Learnt from: hokkyss
Repo: TanStack/router PR: 5418
File: e2e/react-start/custom-identifier-prefix/public/site.webmanifest:2-3
Timestamp: 2025-10-09T12:59:14.842Z
Learning: In e2e test fixtures (files under e2e directories), empty or placeholder values in configuration files like site.webmanifest are acceptable and should not be flagged unless the test specifically validates those fields.

Applied to files:

  • e2e/solid-start/css-modules/playwright.config.ts
  • e2e/vue-start/css-modules/playwright.config.ts
  • e2e/react-start/css-modules/playwright.config.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript strict mode with extensive type safety for all code

Applied to files:

  • e2e/react-start/css-modules/tsconfig.json
  • e2e/solid-start/css-modules/tsconfig.json
  • e2e/vue-start/css-modules/tsconfig.json
📚 Learning: 2025-10-09T12:59:02.129Z
Learnt from: hokkyss
Repo: TanStack/router PR: 5418
File: e2e/react-start/custom-identifier-prefix/src/styles/app.css:19-21
Timestamp: 2025-10-09T12:59:02.129Z
Learning: In e2e test directories (paths containing `e2e/`), accessibility concerns like outline suppression patterns are less critical since the code is for testing purposes, not production use.

Applied to files:

  • e2e/vue-start/css-modules/tests/css.spec.ts
  • e2e/solid-start/css-modules/tests/css.spec.ts
  • e2e/react-start/css-modules/tests/css.spec.ts
  • e2e/solid-start/css-modules/.prettierignore
  • e2e/solid-start/css-modules/.gitignore
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Use file-based routing in `src/routes/` directories or code-based routing with route definitions

Applied to files:

  • e2e/solid-start/css-modules/src/routes/modules.tsx
  • e2e/vue-start/css-modules/src/routes/modules.tsx
  • e2e/react-start/css-modules/src/routes/modules.tsx
  • e2e/solid-start/css-modules/src/routes/index.tsx
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Separate framework-agnostic core logic from React/Solid bindings

Applied to files:

  • packages/solid-router/src/HeadContent.tsx
🧬 Code graph analysis (21)
e2e/react-start/css-modules/tests/setup/global.teardown.ts (2)
e2e/solid-start/css-modules/tests/setup/global.teardown.ts (1)
  • teardown (4-6)
e2e/vue-start/css-modules/tests/setup/global.teardown.ts (1)
  • teardown (4-6)
e2e/vue-start/css-modules/src/router.tsx (2)
e2e/react-start/css-modules/src/router.tsx (1)
  • getRouter (4-12)
e2e/solid-start/css-modules/src/router.tsx (1)
  • getRouter (4-12)
e2e/solid-start/css-modules/src/router.tsx (2)
e2e/react-start/css-modules/src/router.tsx (1)
  • getRouter (4-12)
e2e/vue-start/css-modules/src/router.tsx (1)
  • getRouter (4-12)
e2e/react-start/css-modules/tests/setup/global.setup.ts (3)
e2e/solid-start/css-modules/tests/setup/global.setup.ts (1)
  • setup (4-6)
e2e/vue-start/css-modules/tests/setup/global.setup.ts (1)
  • setup (4-6)
packages/vue-router/src/HeadContent.tsx (2)
  • setup (17-41)
  • setup (186-204)
e2e/react-start/css-modules/src/router.tsx (2)
e2e/solid-start/css-modules/src/router.tsx (1)
  • getRouter (4-12)
e2e/vue-start/css-modules/src/router.tsx (1)
  • getRouter (4-12)
e2e/solid-start/css-modules/tests/setup/global.teardown.ts (2)
e2e/react-start/css-modules/tests/setup/global.teardown.ts (1)
  • teardown (4-6)
e2e/vue-start/css-modules/tests/setup/global.teardown.ts (1)
  • teardown (4-6)
e2e/vue-start/css-modules/tests/setup/global.setup.ts (2)
e2e/react-start/css-modules/tests/setup/global.setup.ts (1)
  • setup (4-6)
e2e/solid-start/css-modules/tests/setup/global.setup.ts (1)
  • setup (4-6)
packages/vue-router/src/HeadContent.tsx (1)
packages/router-core/src/router.ts (1)
  • state (1104-1106)
packages/start-plugin-core/src/dev-server-plugin/plugin.ts (3)
packages/start-plugin-core/src/types.ts (1)
  • GetConfigFn (29-33)
packages/virtual-file-routes/src/api.ts (1)
  • route (66-84)
packages/start-plugin-core/src/dev-server-plugin/dev-styles.ts (1)
  • collectDevStyles (38-74)
e2e/solid-start/css-modules/src/routes/modules.tsx (3)
e2e/react-start/css-modules/src/routes/modules.tsx (1)
  • Route (4-6)
e2e/solid-start/css-modules/src/routes/__root.tsx (1)
  • Route (10-18)
e2e/vue-start/css-modules/src/routes/modules.tsx (1)
  • Route (4-6)
e2e/solid-start/css-modules/src/routes/__root.tsx (2)
e2e/react-start/css-modules/src/routes/__root.tsx (1)
  • Route (9-17)
packages/solid-router/src/HeadContent.tsx (1)
  • HeadContent (231-240)
e2e/vue-start/css-modules/src/routes/modules.tsx (3)
e2e/react-start/css-modules/src/routes/modules.tsx (1)
  • Route (4-6)
e2e/solid-start/css-modules/src/routes/modules.tsx (1)
  • Route (4-6)
e2e/vue-start/css-modules/src/routes/index.tsx (1)
  • Route (4-6)
e2e/vue-start/css-modules/tests/setup/global.teardown.ts (2)
e2e/react-start/css-modules/tests/setup/global.teardown.ts (1)
  • teardown (4-6)
e2e/solid-start/css-modules/tests/setup/global.teardown.ts (1)
  • teardown (4-6)
e2e/react-start/css-modules/src/routes/modules.tsx (2)
e2e/solid-start/css-modules/src/routes/modules.tsx (1)
  • Route (4-6)
e2e/vue-start/css-modules/src/routes/modules.tsx (1)
  • Route (4-6)
e2e/solid-start/css-modules/src/routes/index.tsx (2)
e2e/react-start/css-modules/src/routes/index.tsx (1)
  • Route (4-6)
e2e/vue-start/css-modules/src/routes/index.tsx (1)
  • Route (4-6)
packages/solid-router/src/HeadContent.tsx (1)
packages/router-core/src/router.ts (1)
  • state (1104-1106)
e2e/solid-start/css-modules/tests/setup/global.setup.ts (2)
e2e/react-start/css-modules/tests/setup/global.setup.ts (1)
  • setup (4-6)
e2e/vue-start/css-modules/tests/setup/global.setup.ts (1)
  • setup (4-6)
e2e/vue-start/css-modules/playwright.config.ts (1)
examples/solid/quickstart-esbuild-file-based/build.js (1)
  • isDev (6-6)
e2e/vue-start/css-modules/src/routes/__root.tsx (1)
packages/vue-router/src/HeadContent.tsx (1)
  • HeadContent (184-205)
e2e/react-start/css-modules/playwright.config.ts (1)
examples/solid/quickstart-esbuild-file-based/build.js (1)
  • isDev (6-6)
packages/react-router/src/HeadContent.tsx (3)
packages/react-router/src/useRouter.tsx (1)
  • useRouter (16-25)
packages/react-router/src/useRouterState.tsx (1)
  • useRouterState (43-71)
packages/react-router/src/Asset.tsx (1)
  • Asset (11-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Preview
  • GitHub Check: Test
🔇 Additional comments (53)
e2e/vue-start/css-modules/tsconfig.json (2)

3-23: LGTM! Compiler options are well-configured.

The compiler options are correctly set up for a Vue 3 + Vite project with:

  • Strict mode enabled (aligns with TypeScript best practices)
  • Proper Vue 3 JSX configuration (jsx: preserve, jsxImportSource: vue)
  • Vite-compatible settings (isolatedModules, noEmit, moduleResolution: Bundler)
  • Modern ES targets and module system
  • Appropriate path alias configuration

2-2: No changes needed to the include pattern. This project uses a JSX/TSX approach with Vue 3 rather than Single File Components (.vue), so the current pattern ["**/*.ts", "**/*.tsx"] correctly covers all the TypeScript files that need type checking.

Likely an incorrect or invalid review comment.

e2e/vue-start/css-modules/src/router.tsx (1)

1-2: Imports look correct and minimal.
Nothing suspicious here; keeping routeTree sourced from the generated file is consistent with the e2e setup.

e2e/react-start/css-modules/.prettierignore (1)

1-1: LGTM!

Correctly excludes the autogenerated route tree from Prettier formatting to prevent unnecessary churn.

e2e/solid-start/css-modules/.gitignore (1)

1-7: LGTM!

Comprehensive and appropriate ignore patterns for an e2e test project. Properly excludes autogenerated files, build artifacts, and test outputs. Based on learnings, the port tracking pattern aligns with Unix-specific e2e test coordination needs.

e2e/vue-start/css-modules/.gitignore (1)

1-7: LGTM!

Mirrors the solid-start configuration appropriately, maintaining consistency across e2e CSS Modules projects. All patterns are correct and necessary.

e2e/react-start/css-modules/.gitignore (1)

1-7: LGTM! Appropriate ignore patterns for e2e test project.

All entries properly exclude build artifacts, generated files, and test results that should not be committed to version control.

e2e/vue-start/css-modules/.prettierignore (1)

1-1: LGTM! Correctly excludes autogenerated route tree from formatting.

This follows the established pattern across other CSS Modules e2e projects in the repository.

e2e/react-start/css-modules/src/router.tsx (1)

1-12: LGTM! Standard router configuration for e2e testing.

The implementation correctly follows the established pattern across all framework variants (React, Solid, Vue) with appropriate framework-specific imports and consistent router configuration.

e2e/solid-start/css-modules/.prettierignore (1)

1-1: LGTM! Correctly excludes autogenerated route tree from formatting.

Consistent with the pattern established across all CSS Modules e2e projects.

e2e/solid-start/css-modules/src/router.tsx (1)

1-12: LGTM! Standard router configuration for Solid e2e testing.

The implementation correctly uses the Solid-specific router import while maintaining consistency with the React and Vue versions. Router configuration is appropriate for e2e testing purposes.

e2e/solid-start/css-modules/vite.config.ts (1)

1-17: LGTM! Clean e2e test configuration.

The Vite configuration is well-structured for testing CSS Modules with Solid Start SSR. The plugin ordering is correct (tsConfigPaths → tanstackStart → viteSolid), and SSR is explicitly enabled.

e2e/vue-start/css-modules/vite.config.ts (1)

1-17: LGTM! Consistent e2e test configuration.

The Vue Vite configuration follows the same clean structure as the Solid config, with appropriate Vue-specific plugins (vueJsx). Plugin ordering is correct.

e2e/solid-start/css-modules/package.json (1)

1-31: LGTM! Follows workspace and e2e conventions.

The package.json correctly uses the workspace:^ protocol for internal dependencies and follows the established e2e test script patterns. Unix-specific commands in scripts are acceptable for CI environments.

Based on learnings, e2e test scripts are designed for CI Unix environments.

e2e/react-start/css-modules/tsconfig.json (1)

1-22: LGTM! Proper strict TypeScript configuration.

The TypeScript configuration correctly enables strict mode and uses appropriate compiler options for a React + Vite e2e test project. The jsx: "react-jsx" setting is correct for React, and path mappings align with the vite-tsconfig-paths plugin.

As per coding guidelines, TypeScript strict mode is enabled.

e2e/solid-start/css-modules/src/routes/__root.tsx (2)

10-18: LGTM! Proper root route definition.

The root route is correctly structured with appropriate head metadata and component assignment for Solid Start.


20-62: LGTM! Well-structured root component for CSS Modules testing.

The RootComponent correctly implements the full HTML document structure with:

  • Proper placement of HydrationScript and HeadContent (which includes DevStylesLink in dev mode)
  • Test IDs for e2e verification (main-nav, nav-home, nav-modules)
  • Correct Outlet placement for nested routes
  • Scripts at the end of the body

This structure aligns with the PR's objective to fix FOUC by ensuring CSS is properly injected during SSR.

e2e/vue-start/css-modules/package.json (1)

4-15: sideEffects: false may be risky for CSS-based e2e fixtures—please confirm it can’t drop CSS.

Given this project exists specifically to validate CSS (global + modules) behavior, please confirm no bundler/test path (now or future) relies on sideEffects in a way that could tree-shake CSS imports.
Based on learnings, the Unix-specific script syntax (rm -rf, $PORT, inline env var) is acceptable for these e2e projects.

e2e/vue-start/css-modules/src/styles/global.css (1)

1-19: LGTM for e2e global styles (simple, deterministic selectors).

e2e/react-start/css-modules/src/styles/card.module.css (1)

1-20: LGTM for CSS Module fixture; properties are clear for SSR/hydration assertions.

e2e/react-start/css-modules/tests/setup/global.setup.ts (1)

1-6: No changes needed. JSON import attributes (with { type: 'json' }) are reliably supported throughout this repository's e2e test infrastructure and are used consistently across 150+ files in global setup files, teardown files, and Playwright configs. The tsconfig configuration (ES2020 target with Bundler module resolution) and widespread successful usage across multiple e2e frameworks (React, Vue, Solid) confirm this syntax works in the CI environment.

Likely an incorrect or invalid review comment.

e2e/vue-start/css-modules/src/styles/card.module.css (1)

1-20: LGTM! Clean CSS Module for e2e testing.

The CSS Module is well-structured with semantic color comments and appropriate styling for testing CSS Module behavior in development mode.

e2e/react-start/css-modules/src/styles/global.css (1)

1-19: LGTM! Clean global CSS for e2e testing.

The global CSS file is well-structured and serves its purpose for testing global CSS collection in development mode.

e2e/vue-start/css-modules/tests/setup/global.teardown.ts (1)

1-6: LGTM! Consistent with other framework teardown implementations.

The teardown function correctly follows the established pattern and properly stops the dummy server using the package name.

e2e/solid-start/css-modules/tests/setup/global.setup.ts (1)

1-6: LGTM! Consistent setup pattern across frameworks.

The setup function correctly starts the dummy server using the package name and follows the same pattern used in React and Vue e2e projects.

e2e/react-start/css-modules/tests/setup/global.teardown.ts (1)

1-6: LGTM! Consistent teardown pattern.

The teardown logic correctly stops the dummy server and follows the same pattern used across all framework e2e projects. The project's TypeScript version (5.9.0) and Node.js version (22.10.2) both support the JSON import attributes syntax (with { type: 'json' }), and the tsconfig.json is properly configured with module: "ESNext", moduleResolution: "Bundler", and resolveJsonModule: true.

e2e/vue-start/css-modules/tests/setup/global.setup.ts (2)

4-6: Setup flow matches the other e2e suites.
Starting the dummy server keyed by packageJson.name is consistent with the react/solid setups.


1-2: Remove this review comment—no changes needed.

The code uses the standard and modern with { type: 'json' } syntax, which is fully supported in Node.js 24.8.0 and TypeScript with ESNext target. This pattern is consistently used across all playwright.config.ts files in the e2e directory (e.g., react-router examples). The proposed change to assert { type: 'json' } would move to the older, deprecated syntax—the opposite direction from modern best practices. Playwright global setup handles this syntax without issue.

Likely an incorrect or invalid review comment.

e2e/solid-start/css-modules/src/styles/global.css (1)

1-19: Nice: deterministic, easy-to-assert global styles for SSR/dev collection tests.
Class names are clear and the properties should produce stable computed styles.

e2e/solid-start/css-modules/tests/setup/global.teardown.ts (2)

4-6: Teardown is clean and symmetric with setup.


1-2: No changes needed. The with { type: 'json' } syntax is the correct and stable form for Node.js v23.1.0 onward, and your project requires Node.js 24.8.0. The older assert { type: 'json' } form was the experimental syntax that has been superseded. This import statement is consistent with 50+ other e2e test files across the codebase and will execute without issues.

e2e/solid-start/css-modules/src/styles/card.module.css (1)

1-20: Looks good for CSS Modules coverage.
Simple selectors and stable properties make e2e assertions reliable.

e2e/solid-start/css-modules/src/routes/index.tsx (2)

4-25: Route + markup are test-friendly and Solid-correct (class, data-testid).
This should exercise “CSS present even with JS disabled” as intended for the FOUC regression.


1-2: No issue found: ~ alias is properly configured.

The ~ alias is already defined in tsconfig.json as "~/*": ["./src/*"], and the vite-tsconfig-paths plugin in vite.config.ts is configured to resolve these paths. The import import '~/styles/global.css' will correctly resolve to ./src/styles/global.css without compilation failure.

Likely an incorrect or invalid review comment.

e2e/vue-start/css-modules/src/routes/modules.tsx (1)

1-27: LGTM! Vue route with CSS Modules correctly implemented.

The route module correctly uses Vue-specific JSX syntax (class attribute), follows TanStack file-based routing patterns, and properly imports/applies CSS Module classes for SSR testing.

e2e/solid-start/css-modules/src/routes/modules.tsx (1)

1-27: LGTM! Solid route with CSS Modules correctly implemented.

The route module correctly uses Solid-specific JSX syntax (class attribute), follows TanStack file-based routing patterns, and maintains consistency with the Vue implementation.

e2e/react-start/css-modules/src/routes/index.tsx (1)

1-25: LGTM! React route with global CSS correctly implemented.

The route module correctly uses React-specific JSX syntax (className attribute), follows TanStack file-based routing patterns, and properly imports global CSS for SSR testing.

e2e/vue-start/css-modules/src/routes/index.tsx (1)

1-25: LGTM! Vue route with global CSS correctly implemented.

The route module correctly uses Vue-specific JSX syntax (class attribute), follows TanStack file-based routing patterns, and maintains consistency with the React implementation while using framework-appropriate syntax.

e2e/react-start/css-modules/vite.config.ts (1)

10-16: The current plugin order is correct and aligns with all other TanStack Start examples.

The tanstackStart() plugin is intentionally placed before viteReact() across all 16+ react-start vite.config files in the codebase. The plugin implementation uses enforce: 'pre' to ensure it runs in the pre-processing phase before normal plugins, which is necessary for its configuration to apply correctly during JSX transformation.

Likely an incorrect or invalid review comment.

e2e/solid-start/css-modules/playwright.config.ts (2)

9-40: LGTM! Playwright configuration is well-structured.

The configuration properly:

  • Sets up dynamic port allocation via getTestServerPort
  • Configures environment variables for the test server
  • Uses appropriate conditional logic for dev vs. production mode
  • Follows the established pattern from other framework e2e configs in this PR

3-6: Module configuration is correctly set for import attributes and top-level await.

The package is configured as an ES module ("type": "module" in package.json) with TypeScript module set to ESNext, which supports both the import attributes syntax and top-level await on lines 3 and 6.

e2e/react-start/css-modules/src/routes/__root.tsx (1)

1-60: LGTM! Root route structure is correct for React 19 SSR.

The implementation properly:

  • Uses React 19's document rendering capabilities (returning <html> directly)
  • Includes HeadContent which will inject dev styles to prevent FOUC (per PR objective)
  • Provides navigation with test IDs for e2e verification
  • Renders Outlet for nested routes and Scripts for hydration

The structure aligns with the PR's goal of collecting and serving CSS during SSR to address issue #3023.

e2e/react-start/css-modules/tests/css.spec.ts (1)

1-137: Test coverage is comprehensive and addresses the PR objectives well.

The test suite effectively validates:

  • ✅ Global CSS and CSS Modules work on initial SSR load (with JS disabled)
  • ✅ CSS Modules generate scoped/hashed class names
  • ✅ Global CSS class names remain unscoped
  • ✅ Styles persist after hydration (preventing FOUC)
  • ✅ Styles work correctly during client-side navigation

This directly addresses issue #3023's requirement for CSS Modules support without flash of unstyled content.

e2e/vue-start/css-modules/src/routes/__root.tsx (1)

1-62: LGTM! Vue root route structure is correct and consistent with React implementation.

The implementation:

  • Properly uses Vue Router's Html and Body components for SSR
  • Includes HeadContent which will inject DevStylesLink in development (per snippet from packages/vue-router/src/HeadContent.tsx)
  • Maintains structural consistency with the React version while using framework-appropriate APIs
  • Provides the same navigation and test IDs for consistent e2e testing across frameworks

This ensures CSS Modules work correctly in Vue Start applications, achieving cross-framework feature parity.

e2e/react-start/css-modules/src/routes/modules.tsx (2)

8-27: LGTM! CSS Modules usage is correct and testable.

The component:

  • Properly imports and applies CSS module classes via the styles object
  • Includes appropriate test IDs (module-card, module-title, module-content) that match the expectations in tests/css.spec.ts
  • Clearly documents the purpose (testing CSS Modules in SSR)
  • Uses scoped class names that will be validated by the e2e tests

2-2: The ~ path alias is correctly configured. The tsconfig.json defines "paths": { "~/*": ["./src/*"] } and the vite.config.ts uses the vite-tsconfig-paths plugin, which automatically resolves the tsconfig paths at runtime. The import statement resolves correctly to the styles directory.

e2e/react-start/css-modules/package.json (1)

17-18: LGTM! Correct workspace protocol usage.

The workspace:^ protocol is correctly used for all internal TanStack dependencies, aligning with the coding guidelines for this repository.

Also applies to: 24-24

e2e/react-start/css-modules/playwright.config.ts (1)

1-40: LGTM! Well-structured Playwright configuration.

The configuration correctly uses:

  • Modern import attributes syntax for JSON imports
  • Top-level await for async port resolution
  • Environment-aware command switching between dev and production modes
  • Proper test isolation with single worker
e2e/vue-start/css-modules/playwright.config.ts (1)

1-40: LGTM! Consistent configuration across frameworks.

The Vue Playwright configuration follows the same well-structured pattern as the React and Solid versions, ensuring consistent test execution across all three framework implementations.

packages/react-router/src/HeadContent.tsx (1)

226-233: Consider basepath-awareness for the dev CSS endpoint href.

Hard-coding href to "/@tanstack-start/styles.css?...“ may fail when the app is served from a non-root base path.

  • Does TanStack Start expose a basepath (router option) that should prefix this URL in dev?

Also applies to: 248-253

packages/vue-router/src/HeadContent.tsx (1)

189-203: Dev-only prepend ordering looks good. Keeps the dev link ahead of route-managed tags.

packages/solid-router/src/HeadContent.tsx (1)

1-4: Verify whether duplicate imports from solid-js are allowed by lint rules.

This file imports both import * as Solid from 'solid-js' and import { For, onMount } from 'solid-js'. If import/no-duplicates is enabled, CI may fail.

packages/start-plugin-core/src/dev-server-plugin/dev-styles.ts (1)

89-97: Verify transformRequest(..., { ssr: true }) is a supported Vite API in this repo’s Vite version.

If unsupported, node.ssrTransformResult may never populate and dependency crawling will miss modules.

Comment on lines +16 to +20
"dependencies": {
"@tanstack/vue-router": "workspace:^",
"@tanstack/vue-start": "workspace:^",
"vue": "^3.5.16"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use workspace:* for internal dependencies (per repo guidelines).

@tanstack/vue-router, @tanstack/vue-start, and @tanstack/router-e2e-utils should use workspace:* instead of workspace:^ to match the repo rule for **/package.json.

Proposed diff
   "dependencies": {
-    "@tanstack/vue-router": "workspace:^",
-    "@tanstack/vue-start": "workspace:^",
+    "@tanstack/vue-router": "workspace:*",
+    "@tanstack/vue-start": "workspace:*",
     "vue": "^3.5.16"
   },
   "devDependencies": {
     "@playwright/test": "^1.50.1",
-    "@tanstack/router-e2e-utils": "workspace:^",
+    "@tanstack/router-e2e-utils": "workspace:*",
     "@types/node": "^22.10.2",
     "@vitejs/plugin-vue-jsx": "^4.1.2",
     "srvx": "^0.10.0",
     "typescript": "^5.7.2",
     "vite": "^7.1.7",
     "vite-tsconfig-paths": "^5.1.4"
   }
As per coding guidelines, `**/package.json` should use workspace protocol `workspace:*` for internal dependencies.

Also applies to: 21-30

🤖 Prompt for AI Agents
In @e2e/vue-start/css-modules/package.json around lines 16 - 20, Update the
internal dependency versions in package.json that use the workspace protocol
from "workspace:^" to "workspace:*" — specifically replace
"@tanstack/vue-router", "@tanstack/vue-start", and "@tanstack/router-e2e-utils"
(and any other internal @tanstack/* entries on lines 21-30) to use "workspace:*"
so they follow the repo guideline for internal workspace dependencies.

Comment on lines +146 to +158
async function loadCssContent(
vite: ViteDevServer,
node: ModuleNode,
): Promise<string | undefined> {
// For ALL CSS files (including CSS modules), get the transformed content
// and extract __vite__css. Vite's transform puts the final CSS (with hashed
// class names for modules) into the __vite__css variable.
const transformResult = await vite.transformRequest(node.url)
if (!transformResult?.code) return undefined

// Extract CSS content from Vite's transformed module
return extractCssFromViteModule(transformResult.code)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make CSS loading best-effort (don’t let one transform failure break the whole response).

vite.transformRequest(node.url) can throw; right now that would reject traversal and likely break the /styles.css request entirely.

Proposed fix
 async function loadCssContent(
   vite: ViteDevServer,
   node: ModuleNode,
 ): Promise<string | undefined> {
   // For ALL CSS files (including CSS modules), get the transformed content
   // and extract __vite__css. Vite's transform puts the final CSS (with hashed
   // class names for modules) into the __vite__css variable.
-  const transformResult = await vite.transformRequest(node.url)
+  let transformResult: Awaited<ReturnType<ViteDevServer['transformRequest']>>
+  try {
+    transformResult = await vite.transformRequest(node.url)
+  } catch {
+    return undefined
+  }
   if (!transformResult?.code) return undefined

   // Extract CSS content from Vite's transformed module
   return extractCssFromViteModule(transformResult.code)
 }
🤖 Prompt for AI Agents
In @packages/start-plugin-core/src/dev-server-plugin/dev-styles.ts around lines
146 - 158, The loadCssContent function currently calls
vite.transformRequest(node.url) which can throw and break the entire styles
traversal; wrap that call in a try/catch inside loadCssContent so any thrown
error is caught, optionally log the error (or debug) and return undefined to
make CSS loading best-effort, and keep the existing check for
transformResult?.code before calling extractCssFromViteModule; ensure no
transformRequest exceptions bubble out of loadCssContent.

Comment on lines +79 to +124
// Middleware to serve collected CSS for dev mode
// Security: Route IDs from query params are validated against TSS_ROUTES_MANIFEST.
// Only routes that exist in the manifest will have their CSS collected.
// Arbitrary file paths cannot be injected.
viteDevServer.middlewares.use(async (req, res, next) => {
const url = req.url ?? ''
if (!url.startsWith('/@tanstack-start/styles.css')) {
return next()
}

// Parse route IDs from query param
const urlObj = new URL(url, 'http://localhost')
const routesParam = urlObj.searchParams.get('routes')
const routeIds = routesParam ? routesParam.split(',') : []

// Build entries list from route file paths
const entries: Array<string> = []

// Look up route file paths from manifest
// Only routes registered in the manifest are used - this prevents path injection
const routesManifest = (globalThis as any).TSS_ROUTES_MANIFEST as
| Record<string, { filePath: string; children?: Array<string> }>
| undefined

if (routesManifest && routeIds.length > 0) {
for (const routeId of routeIds) {
const route = routesManifest[routeId]
if (route?.filePath) {
entries.push(route.filePath)
}
}
}

const css =
entries.length > 0
? await collectDevStyles({
viteDevServer,
entries,
})
: undefined

res.setHeader('Content-Type', 'text/css')
res.setHeader('Cache-Control', 'no-store')
res.end(css ?? '')
})

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Harden the dev-styles middleware: strict path match + error handling for async failures.

  • startsWith('/@tanstack-start/styles.css') will also match e.g. styles.css.map or styles.css/anything.
  • new URL(...) and collectDevStyles(...) can throw; right now that can surface as an unhandled async middleware error.
Proposed fix
           viteDevServer.middlewares.use(async (req, res, next) => {
             const url = req.url ?? ''
-            if (!url.startsWith('/@tanstack-start/styles.css')) {
-              return next()
-            }
-
-            // Parse route IDs from query param
-            const urlObj = new URL(url, 'http://localhost')
-            const routesParam = urlObj.searchParams.get('routes')
-            const routeIds = routesParam ? routesParam.split(',') : []
+            let urlObj: URL
+            try {
+              urlObj = new URL(url, 'http://localhost')
+            } catch (err) {
+              return next(err as any)
+            }
+
+            if (urlObj.pathname !== '/@tanstack-start/styles.css') {
+              return next()
+            }
+
+            const routesParam = urlObj.searchParams.get('routes')
+            const routeIds = routesParam ? routesParam.split(',') : []

             // Build entries list from route file paths
             const entries: Array<string> = []

             // Look up route file paths from manifest
             // Only routes registered in the manifest are used - this prevents path injection
             const routesManifest = (globalThis as any).TSS_ROUTES_MANIFEST as
               | Record<string, { filePath: string; children?: Array<string> }>
               | undefined

             if (routesManifest && routeIds.length > 0) {
               for (const routeId of routeIds) {
                 const route = routesManifest[routeId]
                 if (route?.filePath) {
                   entries.push(route.filePath)
                 }
               }
             }

-            const css =
-              entries.length > 0
-                ? await collectDevStyles({
-                    viteDevServer,
-                    entries,
-                  })
-                : undefined
+            let css: string | undefined
+            try {
+              css =
+                entries.length > 0
+                  ? await collectDevStyles({ viteDevServer, entries })
+                  : undefined
+            } catch (err) {
+              return next(err as any)
+            }

             res.setHeader('Content-Type', 'text/css')
             res.setHeader('Cache-Control', 'no-store')
             res.end(css ?? '')
           })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Middleware to serve collected CSS for dev mode
// Security: Route IDs from query params are validated against TSS_ROUTES_MANIFEST.
// Only routes that exist in the manifest will have their CSS collected.
// Arbitrary file paths cannot be injected.
viteDevServer.middlewares.use(async (req, res, next) => {
const url = req.url ?? ''
if (!url.startsWith('/@tanstack-start/styles.css')) {
return next()
}
// Parse route IDs from query param
const urlObj = new URL(url, 'http://localhost')
const routesParam = urlObj.searchParams.get('routes')
const routeIds = routesParam ? routesParam.split(',') : []
// Build entries list from route file paths
const entries: Array<string> = []
// Look up route file paths from manifest
// Only routes registered in the manifest are used - this prevents path injection
const routesManifest = (globalThis as any).TSS_ROUTES_MANIFEST as
| Record<string, { filePath: string; children?: Array<string> }>
| undefined
if (routesManifest && routeIds.length > 0) {
for (const routeId of routeIds) {
const route = routesManifest[routeId]
if (route?.filePath) {
entries.push(route.filePath)
}
}
}
const css =
entries.length > 0
? await collectDevStyles({
viteDevServer,
entries,
})
: undefined
res.setHeader('Content-Type', 'text/css')
res.setHeader('Cache-Control', 'no-store')
res.end(css ?? '')
})
viteDevServer.middlewares.use(async (req, res, next) => {
const url = req.url ?? ''
let urlObj: URL
try {
urlObj = new URL(url, 'http://localhost')
} catch (err) {
return next(err as any)
}
if (urlObj.pathname !== '/@tanstack-start/styles.css') {
return next()
}
// Parse route IDs from query param
const routesParam = urlObj.searchParams.get('routes')
const routeIds = routesParam ? routesParam.split(',') : []
// Build entries list from route file paths
const entries: Array<string> = []
// Look up route file paths from manifest
// Only routes registered in the manifest are used - this prevents path injection
const routesManifest = (globalThis as any).TSS_ROUTES_MANIFEST as
| Record<string, { filePath: string; children?: Array<string> }>
| undefined
if (routesManifest && routeIds.length > 0) {
for (const routeId of routeIds) {
const route = routesManifest[routeId]
if (route?.filePath) {
entries.push(route.filePath)
}
}
}
let css: string | undefined
try {
css =
entries.length > 0
? await collectDevStyles({ viteDevServer, entries })
: undefined
} catch (err) {
return next(err as any)
}
res.setHeader('Content-Type', 'text/css')
res.setHeader('Cache-Control', 'no-store')
res.end(css ?? '')
})
🤖 Prompt for AI Agents
In @packages/start-plugin-core/src/dev-server-plugin/plugin.ts around lines 79 -
124, The middleware should only match the exact styles route and handle async
errors: replace the loose startsWith check with an exact pathname check (e.g.,
ensure new URL(req.url, base).pathname === '/@tanstack-start/styles.css' and
optionally verify req.method === 'GET'), and wrap the URL parsing +
collectDevStyles call in a try/catch so exceptions don't become unhandled; on
error log via viteDevServer.config.logger.error (or console.error) and respond
with a 500 status and an empty body. Keep the existing route manifest lookup
(TSS_ROUTES_MANIFEST) and entries/collectDevStyles usage, but move the new
URL(...) and await collectDevStyles(...) inside the try block and only call
res.setHeader/res.end in the normal path (use res.statusCode = 500; res.end('')
in the catch).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/react-router/tests/Scripts.test.tsx (1)

220-220: Add test coverage for production mode.

The test expectation now includes the dev-styles link, but there's no corresponding test to verify this link is NOT rendered in production mode. Since this is a dev-only feature per the PR objectives, you should add a test that sets process.env.NODE_ENV = 'production' (or the equivalent mechanism) and verifies the dev-styles link is absent from the rendered HTML.

🧪 Suggested test to verify production behavior

Add a new test after this one:

test('dev-styles link is not rendered in production', async () => {
  const originalEnv = process.env.NODE_ENV
  process.env.NODE_ENV = 'production'

  const rootRoute = createRootRoute({
    head: () => ({
      meta: [{ title: 'Root' }],
    }),
    component: () => <HeadContent />,
  })

  const indexRoute = createRoute({
    path: '/',
    getParentRoute: () => rootRoute,
  })

  const router = createRouter({
    history: createMemoryHistory({
      initialEntries: ['/'],
    }),
    routeTree: rootRoute.addChildren([indexRoute]),
    isServer: true,
  })

  await router.load()

  const html = ReactDOMServer.renderToString(
    <RouterProvider router={router} />,
  )
  
  expect(html).not.toContain('data-tanstack-start-dev-styles')
  expect(html).not.toContain('/@tanstack-start/styles.css')

  process.env.NODE_ENV = originalEnv
})
e2e/vue-start/css-modules/src/routes/modules.tsx (1)

9-28: Add explicit return type annotation for better type safety.

The Modules component implementation is correct and follows Vue 3 JSX conventions. However, per the coding guidelines requiring TypeScript strict mode with extensive type safety, consider adding an explicit return type annotation.

♻️ Proposed enhancement for type safety
-function Modules() {
+function Modules(): JSX.Element {
   return (
     <div>
       <h1>CSS Collection Test - CSS Modules</h1>

As per coding guidelines, TypeScript strict mode with extensive type safety should be used for all **/*.{ts,tsx} files.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e231ba1 and ae8fd59.

📒 Files selected for processing (4)
  • e2e/react-start/css-modules/src/routes/modules.tsx
  • e2e/solid-start/css-modules/src/routes/modules.tsx
  • e2e/vue-start/css-modules/src/routes/modules.tsx
  • packages/react-router/tests/Scripts.test.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • e2e/react-start/css-modules/src/routes/modules.tsx
  • e2e/solid-start/css-modules/src/routes/modules.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript strict mode with extensive type safety for all code

Files:

  • packages/react-router/tests/Scripts.test.tsx
  • e2e/vue-start/css-modules/src/routes/modules.tsx
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Implement ESLint rules for router best practices using the ESLint plugin router

Files:

  • packages/react-router/tests/Scripts.test.tsx
  • e2e/vue-start/css-modules/src/routes/modules.tsx
🧠 Learnings (7)
📓 Common learnings
Learnt from: nlynzaad
Repo: TanStack/router PR: 6215
File: e2e/react-start/custom-basepath/package.json:13-17
Timestamp: 2025-12-25T13:04:55.492Z
Learning: In the TanStack Router repository, e2e test scripts are specifically designed to run in CI (which uses a Unix environment), so Unix-specific commands (like `rm -rf`, `&` for backgrounding, and direct environment variable assignments without `cross-env`) are acceptable in e2e test npm scripts.
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • packages/react-router/tests/Scripts.test.tsx
  • e2e/vue-start/css-modules/src/routes/modules.tsx
📚 Learning: 2025-12-25T13:04:55.492Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 6215
File: e2e/react-start/custom-basepath/package.json:13-17
Timestamp: 2025-12-25T13:04:55.492Z
Learning: In the TanStack Router repository, e2e test scripts are specifically designed to run in CI (which uses a Unix environment), so Unix-specific commands (like `rm -rf`, `&` for backgrounding, and direct environment variable assignments without `cross-env`) are acceptable in e2e test npm scripts.

Applied to files:

  • packages/react-router/tests/Scripts.test.tsx
📚 Learning: 2025-11-02T16:16:24.898Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5732
File: packages/start-client-core/src/client/hydrateStart.ts:6-9
Timestamp: 2025-11-02T16:16:24.898Z
Learning: In packages/start-client-core/src/client/hydrateStart.ts, the `import/no-duplicates` ESLint disable is necessary for imports from `#tanstack-router-entry` and `#tanstack-start-entry` because both aliases resolve to the same placeholder file (`fake-start-entry.js`) in package.json during static analysis, even though they resolve to different files at runtime.

Applied to files:

  • packages/react-router/tests/Scripts.test.tsx
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{js,ts,tsx} : Implement ESLint rules for router best practices using the ESLint plugin router

Applied to files:

  • e2e/vue-start/css-modules/src/routes/modules.tsx
📚 Learning: 2025-12-17T02:17:55.086Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 6120
File: packages/router-generator/src/generator.ts:654-657
Timestamp: 2025-12-17T02:17:55.086Z
Learning: In `packages/router-generator/src/generator.ts`, pathless_layout routes must receive a `path` property when they have a `cleanedPath`, even though they are non-path routes. This is necessary because child routes inherit the path from their parent, and without this property, child routes would not have the correct full path at runtime.

Applied to files:

  • e2e/vue-start/css-modules/src/routes/modules.tsx
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Use file-based routing in `src/routes/` directories or code-based routing with route definitions

Applied to files:

  • e2e/vue-start/css-modules/src/routes/modules.tsx
🧬 Code graph analysis (1)
e2e/vue-start/css-modules/src/routes/modules.tsx (3)
e2e/react-start/css-modules/src/routes/modules.tsx (1)
  • Route (5-7)
e2e/solid-start/css-modules/src/routes/modules.tsx (1)
  • Route (5-7)
e2e/vue-start/css-modules/src/routes/__root.tsx (1)
  • Route (11-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (2)
e2e/vue-start/css-modules/src/routes/modules.tsx (2)

2-7: LGTM!

The imports and route definition follow the correct file-based routing pattern and are consistent with the React and Solid implementations.


1-1: Vite client reference correctly placed and consistent across framework implementations.

The triple-slash directive is properly positioned on line 1 and matches the implementation in the React and Solid versions, enabling CSS Modules collection in development mode.

@schiller-manuel schiller-manuel merged commit 5e0ab7a into main Jan 10, 2026
5 of 6 checks passed
@schiller-manuel schiller-manuel deleted the dev-styles branch January 10, 2026 03:07
Copy link

@dgreif dgreif left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for getting this added! It will make a huge difference in DX. Just pointing out a few small bumps I ran into when trying it out

}, [])

// Build the same href on both server and client for hydration match
const href = `/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(','))}`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't appear to use the vite base path if one is set, leading to a 404 when trying to fetch the styles

Comment on lines 64 to 70
if (
!isRunnableDevEnvironment(serverEnv) ||
// do not check via `isFetchableDevEnvironment` since nitro does implement the `FetchableDevEnvironment` interface but not via inheritance (which this helper checks)
'dispatchFetch' in serverEnv
) {
return
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed this return gets hit if I have the nitro plugin running as well, causing the new middleware to not get registered, leading to a 404 for /@tanstack-start/styles.css

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

How to use CSS Modules with TanStack Start?

3 participants